#pragma once
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <functional>
#include <string>

namespace ThreadModule
{
    template<typename Func, typename ...Args>
    class Thread
    {
        using RetType = std::invoke_result_t<Func, Args...>;
    public:
        Thread(Func&& func, Args&&... args)
            : _func(std::bind(std::forward<Func>(func), std::forward<Args>(args)...))
        {
            _Start();
        }

        ~Thread()
        {
            if(_is_start && !_is_join)
            {
                pthread_detach(_tid);
            }
        }
    public:
        void* Join()
        {
            if(!_is_start || _is_join) { return nullptr; }

            void* ret = nullptr;
            int n = pthread_join(_tid, &ret);
            if(n != 0) { perror("error: Join()::pthread_join()"); exit(1);}

            _is_join = true;
            return ret;
        }

    private:
        // The purpose of declaring static is to prevent function arguments from passing this pointer by default, 
            // which does not meet the pthread_create call condition
        static void* handler(void* f)
        {
            auto p = static_cast<std::function<RetType()>*>(f);
            auto ret = (*p)();   // call _func function
            delete p;
            return new RetType(ret);
        }
        bool _Start()
        {
            int n = pthread_create(&_tid, nullptr, handler, new std::function<RetType(Args...)>(_func));
            _is_start = (n == 0)? true: false;
            return _is_start;
        }

    private:
        pthread_t _tid;
        std::function<RetType(Args...)> _func;
        bool _is_start = false;
        bool _is_join = false;
    };
}


